#pragma once
#include "Log.hpp"
#include <iostream>
#include <mysql/mysql.h>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>
#include <fstream>
#include <vector>
using namespace LogModel;

class MySQL_Util
{
public:
    static MYSQL *mysql_create(const std::string &host,
                               const std::string &user,
                               const std::string &passwd,
                               const std::string &dbname,
                               unsigned int port = 3306)
    {
        // 1.mysql句柄的初始化
        MYSQL *mysql_handle = mysql_init(NULL);
        if (mysql_handle == NULL)
        {
            LOG(LogLevel::FATAL) << "mysql_init error!";
            return NULL;
        }
        // 2.连接mysql数据库服务器
        MYSQL *ret = mysql_real_connect(mysql_handle,
                                        host.c_str(), user.c_str(), passwd.c_str(), dbname.c_str(), port, NULL, 0);
        if (ret == NULL)
        {
            LOG(LogLevel::FATAL) << "mysql_real_connect error, error cause: " << mysql_error(mysql_handle);
            mysql_destory(mysql_handle);
            return NULL;
        }

        // 3.设置本地字符集编码格式
        int ret1 = mysql_set_character_set(mysql_handle, "utf8");
        if (ret1 != 0)
        {
            LOG(LogLevel::FATAL) << "mysql_set_character_set error, error cause: " << mysql_error(mysql_handle);
            mysql_destory(mysql_handle);
            return NULL;
        }

        // 4.选择要操作的数据库
        mysql_select_db(mysql_handle, dbname.c_str());

        // 返回操作数据库的句柄
        return mysql_handle;
    }
    // 5.执行SQL语句
    static bool mysql_execute(MYSQL *mysql_handle, const std::string &sql)
    {
        int ret = mysql_query(mysql_handle, sql.c_str());
        if (ret != 0)
        {
            LOG(LogLevel::ERROR) << "mysql_query error, error cause: " << mysql_error(mysql_handle);
            return false;
        }
        return true;
    }
    // .销毁mysql句柄
    static void mysql_destory(MYSQL *mysql_handle)
    {
        if (mysql_handle != NULL)
            mysql_close(mysql_handle);
    }
};

class JSON_Util
{
public:
    static bool Serialize(Json::Value &root, std::string &str)
    {
        // 1.实例化出一个StreamWriterBuilder工厂类对象
        Json::StreamWriterBuilder builder;

        // 当没有设置emitUTF8时，JsonCpp会将所有非ASCII字符转换为\uXXXX格式的Unicode转义序列
        // 设置emitUTF8 = true后，JsonCpp会直接输出UTF - 8编码的中文字符
        builder.settings_["emitUTF8"] = true;

        // 2.通过工厂类对象生产一个StreamWriter对象(该对象才是序列化的真正执行者)
        std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter()); // RAII思想

        // 3.使用StreamWriter对象对Json::Value内的数据进行序列化
        std::stringstream ss;
        int ret = writer->write(root, &ss);
        if (ret != 0)
        {
            std::cout << "json error" << std::endl;
            return false;
        }

        // 4.获取序列化的数据,并转换为字符串形式,通过str参数带到外面去
        str = ss.str();

        return true;
    }

    static bool DeSerialize(const std::string &json_str, Json::Value &root)
    {
        // 1.实例化出一个CharReaderBuilder工厂类对象
        Json::CharReaderBuilder builder;
        // 2.通过工厂类对象生产出一个CharReader对象(该对象才是反序列化的真正执行者)
        std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); // RAII思想

        // 3.使用CharReader对象对Json格式的json_str进行反序列化
        // parse的第一个参数:
        const char *start = json_str.c_str();                 // json_str字符串的首地址
        const char *end = json_str.c_str() + json_str.size(); // json_str字符串的尾字符的下一个地址

        Json::String err; // parse的最后一个参数: 解析出错时,错误信息保存在这里

        bool ret = reader->parse(start, end, &root, &err); // 反序列化结果存放在root中,带出外面
        if (!ret)
        {
            LOG(LogLevel::ERROR) << "deserialize error: " << err;
            return false;
        }

        return true;
    }
};

class String_Util
{
public:
    // 将传入的字符串按照指定的分隔符进行分割,得到的子串存入str_res返回
    static int split(const std::string &src, const std::string &sep, std::vector<std::string> &str_res)
    {
        // 如: "12,345,678," ,sep=","
        size_t pos = 0;         // 找到的sep字符串的起始下标
        size_t start_index = 0; // 从哪个位置开始查找
        while (start_index < src.size())
        {
            pos = src.find(sep, start_index);

            // 切完了:
            if (pos == std::string::npos)
            {
                str_res.push_back(src.substr(start_index, src.size() - start_index)); // substr(切割起点,切多大)
                break;
            }

            if (pos == start_index) // 比如出现情况: "1,,,,,,345"  sep=","   此时因为pos-start_index=0,
            {                       //                                      会切出空串
                start_index = pos + sep.size();
                continue;
            }
            // 继续切:
            str_res.push_back(src.substr(start_index, pos - start_index)); // substr(切割起点,切多大)
            // 更新下一个切割起点,注意sep长度
            start_index = pos + sep.size();
        }
        return str_res.size();
    }
};

class File_Util
{
public:
    static bool Read(const std::string &filepath, std::string &body)
    {
        // 1.打开文件
        // ios::binary：二进制模式（避免文本模式的换行符转换）。
        std::ifstream file(filepath.c_str(), std::ios::binary);
        if (!file.is_open())
        {
            LOG(LogLevel::FATAL) << "open file error";
            return false;
        }

        // 2.获取文件的大小
        file.seekg(0, std::ios::end);    // 移动文件指针到文件末尾
        size_t file_size = file.tellg(); // 获取当前指针位置（即文件大小）
        file.seekg(0, std::ios::beg);    // 要将文件指针移动回到文件起点

        // 一次性调整好body的大小(效率更高)
        body.resize(file_size);

        // 3.读取文件内容
        file.read(&body[0], file_size); // &body[0] : 获取空间的首地址
        // 注意: read的第一个参数是char*类型,不能直接传&body【string* 类型】或者 body.c_str() 【const char*类型】

        if (file.good() == false) // good(): 判断上一步操作(读文件)是否成功
        {
            LOG(LogLevel::FATAL) << "file read error";
            file.close();
            return false;
        }

        file.close();
        return true;
    }
};
// ios::binary 模式的核心价值是​​保证文件内容的字节级精确性​​，避免操作系统在文本模式下对换行符、特殊字符的自动转换。
// 其必要性体现在：

// ​1. ​二进制文件处理​​：图像、音频等文件依赖原始字节，必须用二进制模式。
// ​​2. 跨平台兼容性​​：确保文件在不同系统间传输后内容不变。
// ​​3. 特殊字符保留​​：保留文本中的 \r、\n、\x00 等控制字符，避免解析错误。比如\n，本来人家可能是html的某些特定字符,
//    但是却被解析成了换行符